package com.unbiz.tuple;

import java.io.Serializable;
import java.util.AbstractCollection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.IntFunction;

/**
 * @author A N M Bazlur Rahman
 * @since 3/15/15.
 */
public abstract class Tuple<T extends Tuple> implements IntFunction<Object>, Cloneable, Serializable, Comparable<T>, Collection<Object> {
	public static final Tuple<?> EMPTY = new Tuple0();
    
    public static <T0> Tuple1<T0> of(T0 _0) {
        return Tuple1.of(_0);
    }

    public static <T0, T1> Tuple2<T0, T1> of(T0 _0, T1 _1) {
        return Tuple2.of(_0, _1);
    }

    public static <T0, T1, T2> Tuple3<T0, T1, T2> of(T0 _0, T1 _1, T2 _2) {
        return Tuple3.of(_0, _1, _2);
    }

    public static <T0, T1, T2, T3> Tuple4<T0, T1, T2, T3> of(T0 _0, T1 _1, T2 _2, T3 _3) {
        return Tuple4.of(_0, _1, _2, _3);
    }

    public static <T0, T1, T2, T3, T4> Tuple5<T0, T1, T2, T3, T4> of(T0 _0, T1 _1, T2 _2, T3 _3, T4 _4) {
        return Tuple5.of(_0, _1, _2, _3, _4);
    }

    public static <T0, T1, T2, T3, T4, T5> Tuple6<T0, T1, T2, T3, T4, T5> of(T0 _0, T1 _1, T2 _2, T3 _3, T4 _4, T5 _5) {
        return Tuple6.of(_0, _1, _2, _3, _4, _5);
    }

    public static <T0, T1, T2, T3, T4, T5, T6> Tuple7<T0, T1, T2, T3, T4, T5, T6> of(T0 _0, T1 _1, T2 _2, T3 _3, T4 _4, T5 _5, T6 _6) {
        return Tuple7.of(_0, _1, _2, _3, _4, _5, _6);
    }

    public static <T0, T1, T2, T3, T4, T5, T6, T7> Tuple8<T0, T1, T2, T3, T4, T5, T6, T7> of(T0 _0, T1 _1, T2 _2, T3 _3, T4 _4, T5 _5, T6 _6, T7 _7) {
        return Tuple8.of(_0, _1, _2, _3, _4, _5, _6, _7);
    }

    public static <T0, T1, T2, T3, T4, T5, T6, T7, T8> Tuple9<T0, T1, T2, T3, T4, T5, T6, T7, T8> of(T0 _0, T1 _1, T2 _2, T3 _3, T4 _4, T5 _5, T6 _6, T7 _7, T8 _8) {
        return Tuple9.of(_0, _1, _2, _3, _4, _5, _6, _7, _8);
    }

    public abstract int size();
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Tuple)) return false;

        Tuple tuple = (Tuple) o;
        if(tuple.size()!=this.size()) return false;
        for(int i=0;i<size();i++){
    		Object e = this.apply(i);
    		Object e2 = tuple.apply(i);
    		if(e!=null && !e.equals(e2)){
    			return false;
    		}
    	}
        return true;
    }
    

    @Override
    public int compareTo(T o) {
        Objects.requireNonNull(o);
        if (!getClass().equals(o.getClass())) {
            throw new ClassCastException(o.getClass() + " must equal " + getClass());
        }

        for (int i = 0; i < size(); i++) {
            @SuppressWarnings("unchecked")
            Comparable<Object> l = (Comparable<Object>) apply(i);
            Object r = o.apply(i);
            int c = l.compareTo(r);
            if (c != 0) {
                return c;
            }
        }
        return 0;
    }
    
    public <T> T get(int i) {
		if ( i < 0 ) {
			throw new IllegalArgumentException( "requested tuple index must be greater than zero" );
		}
		if ( i >  size() ) {
			throw new IllegalArgumentException( "requested tuple index exceeds actual tuple size" );
		}
		return (T)apply(i);
	}
    
    public <T> T first(){
    	return (T)apply(0);
    }
   
    
    public List<?> toList() {
        return Arrays.asList(toArray());
    }    
  
    /**
     * Returns <tt>true</tt> if this collection contains no elements.
     *
     * @return <tt>true</tt> if this collection contains no elements
     */
    public boolean isEmpty(){
    	return size()==0 || first()==null;
    }

    /**
     * Returns <tt>true</tt> if this collection contains the specified element.
     * More formally, returns <tt>true</tt> if and only if this collection
     * contains at least one element <tt>e</tt> such that
     * <tt>(o==null&nbsp;?&nbsp;e==null&nbsp;:&nbsp;o.equals(e))</tt>.
     *
     * @param o element whose presence in this collection is to be tested
     * @return <tt>true</tt> if this collection contains the specified
     *         element
     * @throws ClassCastException if the type of the specified element
     *         is incompatible with this collection
     *         (<a href="#optional-restrictions">optional</a>)
     * @throws NullPointerException if the specified element is null and this
     *         collection does not permit null elements
     *         (<a href="#optional-restrictions">optional</a>)
     */
    public boolean contains(Object o){
    	for(int i=0;i<size();i++){
    		Object e = this.apply(i);
    		if(e!=null && o.equals(e)){
    			return true;
    		}
    	}
    	return false;
    }
    
    @Override
	public boolean containsAll(Collection<?> c) {
		// TODO Auto-generated method stub		
		for (Object e : c)
            if (!contains(e))
                return false;
        return true;		
	}
  
    public abstract Object[] toArray(); 

	
    public <T> T[] toArray(T[] a) {
        // Estimate size of array; be prepared to see more or fewer elements
        int size = size();
        T[] r = a.length >= size ? a :
                  (T[])java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size);        

        for (int i = 0; i < size; i++) {           
            r[i] = this.get(i);
        }     
        if (a.length > size)
            a[size] = null;
        return r;
    }

    public void forEach(BiConsumer<Integer,Object> action) {
        Objects.requireNonNull(action);
        for (int i = 0; i < size(); i++) {           
        	action.accept(i,this.get(i));
        }    
    }
    

	@Override
	public boolean add(Object e) {
		// TODO Auto-generated method stub
		throw new UnsupportedOperationException();
		//return false;
	}

	@Override
	public final boolean remove(Object o) {
		// TODO Auto-generated method stub
		throw new UnsupportedOperationException();
		//return false;
	}

	

	@Override
	public boolean addAll(Collection<?> c) {
		// TODO Auto-generated method stub
		throw new UnsupportedOperationException();
		//return false;
	}

	@Override
	public final boolean removeAll(Collection<?> c) {
		// TODO Auto-generated method stub
		throw new UnsupportedOperationException();
		//return false;
	}

	@Override
	public boolean retainAll(Collection<?> c) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public void clear() {
		// TODO Auto-generated method stub
		throw new UnsupportedOperationException();
	}
	
    /**
     * Returns an iterator over the elements in this collection.  There are no
     * guarantees concerning the order in which the elements are returned
     * (unless this collection is an instance of some class that provides a
     * guarantee).
     *
     * @return an <tt>Iterator</tt> over the elements in this collection
     */
    public Iterator<Object> iterator(){
    	 return new Itr();
    }

    /**
     * An optimized version of AbstractList.Itr
     */
    private class Itr implements Iterator<Object> {
        int cursor = 0;       // index of next element to return       

        public boolean hasNext() {
            return cursor != size();
        }

        @SuppressWarnings("unchecked")
        public Object next() {           
            int i = cursor;
            if (i >= size())
                throw new NoSuchElementException();           
            cursor = i + 1;
            return apply(i);
        }       

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super Object> consumer) {
            Objects.requireNonNull(consumer);
            final int size = size();
            int i = cursor;
            if (i >= size) {
                return;
            }           
            while (i != size) {
                consumer.accept(apply(i++));
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;            
        }
        
    }

}
